/**
 * 核心逻辑（ws连接、自动重连等）
 * BuildAdmin 客服系统 v1.0.0
 * https://modules.buildadmin.com/kefu
 *
 * Copyright 2024 BuildAdmin
 *
 * BuildAdmin 客服系统不是开源产品，所有文字、图片、样式、风格等版权归 BuildAdmin 客服系统作者所有，如有复制、仿冒、抄袭、盗用，BuildAdmin 和客服系统作者将追究法律责任
 *
 * Released on: April 1, 2024
 */

import { ref, reactive, nextTick, watch } from 'vue'
import router from '@/router'
import { isEmpty, debounce } from 'lodash-es'
import { getUrl } from '@/utils/axios'
import { Local } from '@/utils/storage'
import { ElMessage } from 'element-plus'
import type { messageType } from 'element-plus'
import { BA_KEFU_TOURIST_TOKEN, BA_KEFU_NEW_USER, BA_KEFU_LANG, getStatusText } from './store'
import type { Hooks, State, Stores, Config, Ws, Common, Ring, ReceivedWsMessage, SendWsMessage, Language } from './types'
import { initialize, buildRingUrl } from '@/api/kefu/index'
import type { AxiosRequestConfig } from 'axios'
import { upload } from '@/api/kefu/index'
import { getArrayKey } from '@/utils/common'
import zhCN from './lang/zh-cn'
import enUS from './lang/en-us'
import { getToken } from '../../utils/auth'

export default class keFu {
    /**
     * 钩子
     */
    public hooks: Hooks

    /**
     * 客服系统配置
     */
    public config?: Config

    /**
     * ws相关属性
     */
    public ws: Ws = {
        timer: null,
        ready: false,
        errorMsg: [],
        socketTask: null,
        canConnect: true,
        maxReconnectCount: 3, // 最大重连次数
        currentReconnectCount: 0,
        awaitResponse: [],
    }

    /**
     * 语言相关
     */
    public languages: Record<string, Language> = {
        'zh-cn': zhCN,
        'en-us': enUS
    }

    /**
     * 当前语言
     */
    public currentLang: string = Local.get(BA_KEFU_LANG) || 'en-us'

    /**
     * 公共
     */
    public common: Common = {
        csrTab: null,
        userTab: null,
        messageInputRef: ref(null),
        messageScrollbarRef: ref(null),
        elMessageInstance: null,
        sessionsRef: ref(null),
        sessionsTimer: null,
        notificationStatus: false,
        // 确保激活的会话在视窗以内，这种方案比起直接 scrollIntoView 更好
        intersectionObserver: new IntersectionObserver((entries) => {
            if (['session', 'tourist'].includes(this.common.intersectionObserverType)) {
                if (entries[0].isIntersecting === false) {
                    let activeEl: HTMLElement | null = null
                    if (this.common.intersectionObserverType == 'session') {
                        activeEl = document.querySelector('.sessions .session-item.active')
                    } else {
                        activeEl = document.querySelector('.tourist-list .tourist-item.active')
                    }
                    if (activeEl) {
                        activeEl.scrollIntoView(false)
                        this.common.intersectionObserverType = 'none'
                        this.common.intersectionObserver.unobserve(activeEl)
                    }
                }
            }
        }),
        intersectionObserverType: 'none',
    }

    /**
     * 铃声
     */
    public ring: Ring = {
        context: new window.AudioContext(),
        source: null,
        buffer: null,
        disabled: false,
    }

    public state: State = reactive({
        awaitResponse: false,
        sendMessageLoading: false,
        messageInputValue: '',
        system: {
            switch: true,
            hideLock: false,
        },
        affix: {
            show: true,
            disabled: false,
        },
        newMessage: {
            show: false,
            disabled: false,
            content: '',
        },
        sessionDialog: {
            show: false,
            searchData: [],
            searchDialog: false,
            searchSession: false,
            searchDataPage: 1,
            searchDataNextPage: true,
            searchSessionKeyword: '',
            shake: false,
        },
        userInfo: {
            id: 0,
            avatar: '',
            nickname: this.t('loading'),
            identity: 'tourist',
            status: 'offline',
        },
        sessionInfo: {},
        sessionRecords: {
            data: [],
            page: 1,
            loading: false,
            nextPage: true,
            users: [],
        },
        touristData: [],
        sessionData: [],
        contextMenuType: '',
        activeTourist: 0,
        activeSession: 0,
        activeOptTab: 'base',
        baseInfoForm: {},
        trajectorys: {
            data: [],
            page: 1,
            nextPage: true,
            loading: false,
        },
        kbs: {
            matchInput: '',
            data: [],
            loading: false,
        },
        invitation: {
            show: false,
            count: 0,
            timer: null,
            disabled: false,
        },
        quickReply: [],
        sessionUserInputStatus: false,
        unexpectedRecords: 0,
        independentTab: false,
        tabMode: '',
        transfer: {
            show: false,
            loading: false,
            csrId: 0,
        },
        language: {
            current: this.currentLang,
            list: [
                { value: 'zh-cn', label: '简体中文' },
                { value: 'en-us', label: 'English' }
            ]
        }
    })

    public stores: Stores = {
        userInfo: null,
        adminInfo: null,
    }

    constructor(hooks: Hooks = {}) {
        this.hooks = hooks

        const currentUrlObj = new URL(window.location.href)
        this.state.tabMode = currentUrlObj.searchParams.get('mode')
        this.state.independentTab = currentUrlObj.searchParams.has('independentTab')

        if (!Local.get(BA_KEFU_TOURIST_TOKEN) && currentUrlObj.searchParams.has('baTouristToken')) {
            Local.set(BA_KEFU_TOURIST_TOKEN, currentUrlObj.searchParams.get('baTouristToken'))
        }

        // 检查URL中是否带有语言参数
        if (currentUrlObj.searchParams.has('lang')) {
            const urlLang = currentUrlObj.searchParams.get('lang')
            if (urlLang && this.languages[urlLang]) {
                this.changeLang(urlLang)
            }
        }

        // 发送初始化请求，取得 ws 端口等基本配置信息
        initialize().then((res) => {
            this.config = res.data.config

            if (res.data.config.not_start_paths.includes(router.currentRoute.value.path)) {
                this.hideSystem()
            }

            // 连接WebSocket
            this.socketConnect()

            // 一句话欢迎消息
            if (!Local.get(BA_KEFU_NEW_USER)) {
                this.onNewMessage(this.t('new_user_tip') || this.config!.new_user_tip)
            }

            // 监听路由，发送轨迹
            const debouncedPathWatch = debounce(() => {
                this.sendTrajectory(this.t('visit'))

                // 切换固钉显示状态
                if (res.data.config.not_start_paths.includes(router.currentRoute.value.path)) {
                    this.hideSystem()
                } else {
                    this.showSystem()
                }
            }, 800)
            watch(
                () => router.currentRoute.value.path,
                () => debouncedPathWatch()
            )

            watch(
                () => Local.get('refresh_token') + getToken(),
                () => {
                    // token 变更，重新初始化用户
                    this.socketSend({
                        pathInfo: 'worker/kefu.WsIndex/initialize',
                        baToken: Local.get('refresh_token'),
                        baUserToken: getToken(),
                        baTouristToken: Local.get(BA_KEFU_TOURIST_TOKEN),
                        backend: false,
                        language: this.currentLang
                    })
                }
            )

            typeof this.hooks.initSuccess == 'function' && this.hooks.initSuccess(this.ws, this.config!)
        })
    }

    /**
     * 多语言翻译函数
     */
    t(key: string, params: Record<string, any> = {}): string {
        const lang = this.languages[this.currentLang] || this.languages['zh-cn']
        let text = lang[key] || key

        // 替换参数
        Object.keys(params).forEach(param => {
            text = text.replace(new RegExp(`{${param}}`, 'g'), params[param])
        })

        return text
    }

    /**
     * 切换语言
     */
    changeLang(lang: string): void {
        if (this.languages[lang]) {
            this.currentLang = lang
            this.state.language.current = lang
            Local.set(BA_KEFU_LANG, lang)

            // 如果已连接WS，发送语言更新
            if (this.ws.socketTask && this.ws.socketTask.readyState === 1) {
                this.socketSend({
                    pathInfo: 'worker/kefu.WsMixed/changeLanguage',
                    language: lang,
                    needResponse: false
                })
            }

            // 触发语言变更钩子
            typeof this.hooks.languageChanged == 'function' && this.hooks.languageChanged(lang)
        }
    }

    /*
     * 连接 WebSocket
     * socketTask.readyState:0=连接中,1=已经连接,2=连接正在关闭,3=连接已经关闭或没有连接成功
     */
    socketConnect() {
        if ((this.ws.socketTask && (this.ws.socketTask.readyState == 0 || this.ws.socketTask.readyState == 1)) || !this.ws.canConnect) {
            return false
        }

        if (!this.config?.ws_url && window.location.protocol == 'https:' && this.config?.wss_switch == 'no') {
            this.showTip(this.t('https_wss_required'), 'error', 0)
            return false
        }

        if ('WebSocket' in window) {
            this.ws.socketTask = new WebSocket(this.buildUrl('ws'))

            this.ws.socketTask.onopen = () => {
                // 重新发送所有出错的消息
                if (this.ws.errorMsg.length > 0) {
                    for (const i in this.ws.errorMsg) {
                        this.socketSend(this.ws.errorMsg[i])
                    }
                    this.ws.errorMsg = []
                }

                // 重置属性
                this.ws.ready = false
                this.ws.currentReconnectCount = 0
                this.common.elMessageInstance?.close()
                this.clearWsTimer()

                // 初始化
                this.socketSend({
                    pathInfo: 'worker/kefu.WsIndex/initialize',
                    baToken: Local.get('refresh_token'),
                    baUserToken: getToken(),
                    baTouristToken: Local.get(BA_KEFU_TOURIST_TOKEN),
                    backend: false,
                    language: this.currentLang
                })

                typeof this.hooks.connectionSuccess == 'function' && this.hooks.connectionSuccess(this.ws, this.config!)
            }

            this.ws.socketTask.onmessage = (evt) => {
                const msg = JSON.parse(evt.data)
                this.onMessage(msg)
            }

            this.ws.socketTask.onclose = () => {
                this.ws.ready = false
                this.clearWsTimer()

                // 重连
                this.socketReconnect()
            }

            this.ws.socketTask.onerror = (e) => {
                this.ws.ready = false
                console.error('websocket 错误:', e)
                this.showTip(this.t('websocket_error'), 'error', 0)
            }
        } else {
            this.showTip(this.t('browser_not_support_websocket'), 'error', 0)
        }
    }

    /**
     * 重连 WebSocket
     */
    socketReconnect() {
        if (!this.ws.canConnect) {
            return false
        }

        this.showTip(this.t('reconnecting'), 'warning', 0)

        if (this.ws.currentReconnectCount < this.ws.maxReconnectCount) {
            this.ws.currentReconnectCount++
            if (this.ws.currentReconnectCount == 1) {
                this.socketConnect()
                console.log(this.t('reconnecting_count', {count: this.ws.currentReconnectCount}))
            } else {
                console.log(this.t('reconnect_after_seconds', {seconds: 6, count: this.ws.currentReconnectCount}))
                this.clearWsTimer()
                this.ws.timer = window.setTimeout(() => {
                    this.socketConnect()
                    console.log(this.t('reconnecting_count', {count: this.ws.currentReconnectCount}))
                }, 6000)
            }
        } else {
            // 每18秒重新连接一次
            console.log(this.t('reconnect_after_long_seconds'))
            this.clearWsTimer()
            this.ws.timer = window.setTimeout(() => {
                this.socketConnect()
            }, 18000)
        }
    }

    /**
     * 发送 ws 消息
     */
    socketSend(message: SendWsMessage) {
        if (isEmpty(message)) {
            return
        }

        if (message.needResponse || message.needResponse === undefined) {
            this.state.awaitResponse = true
            this.ws.awaitResponse.push(message.pathInfo)
        }

        if (this.ws.socketTask && this.ws.socketTask.readyState == 1) {
            this.ws.socketTask.send(JSON.stringify(message))
        } else {
            console.log('ws消息发送失败', message)
            this.ws.errorMsg.push(message)
        }
    }

    /**
     * 构建URL地址
     */
    buildUrl(type: 'default' | 'ws') {
        const url = new URL(getUrl())
        const buildFun = new Map([
            [
                'ws',
                () => {
                    const protocol = this.config?.wss_switch == 'yes' ? 'wss://' : 'ws://'
                    const port = this.config?.websocket_port ? ':' + this.config?.websocket_port : ''

                    const query: anyObj = {
                        ...router.currentRoute.value.query,
                        ...router.currentRoute.value.params,
                        href: window.location.href,
                    }

                    return this.addUrlParams(this.config?.ws_url ? this.config.ws_url : protocol + url.hostname + port, query)
                },
            ],
            [
                'default',
                () => {
                    return url.origin
                },
            ],
        ])

        const action = buildFun.get(type) || buildFun.get('default')
        return action!.call(this)
    }

    /**
     * 添加URL参数
     */
    addUrlParams(url: string, params: anyObj) {
        const urlObj = new URL(url)
        for (const key in params) {
            urlObj.searchParams.append(key, params[key])
        }
        return urlObj.toString()
    }

    /**
     * 获取状态文本
     */
    getStatusText(status: string): string {
        return this.t(getStatusText(status))
    }

    /**
     * 添加当前用户与某用户的对话
     */
    addSession(sessionUserId: number) {
        this.socketSend({
            pathInfo: 'worker/kefu.Wsession/addSession',
            sessionUserId: sessionUserId,
        })
    }

    /**
     * 为 A标签 添加 target 属性
     */
    linkAddTarget(className: string) {
        setTimeout(() => {
            const elements = document.querySelectorAll(`.${className} a`)
            for (let i = 0; i < elements.length; i++) {
                if (!elements[i].hasAttribute('target')) {
                    elements[i].setAttribute('target', '_blank')
                }
            }
        }, 500)
    }

    /**
     * 聊天记录滚动条拉到最底部
     */
    pullMessageScrollBar() {
        if (this.common.messageScrollbarRef.value) {
            nextTick(() => {
                this.common.messageScrollbarRef.value.$el.scrollTop = 0
            })
        }
    }

    /**
     * 聚集消息输入框
     */
    messageInputFocus(ms = 300) {
        setTimeout(() => {
            if (this.common.messageInputRef.value) {
                this.common.messageInputRef.value.focus()
                this.common.messageInputRef.value.setSelectionRange(this.state.messageInputValue.length, this.state.messageInputValue.length)
            }
        }, ms)
    }

    /**
     * 发送消息
     */
    sendSessionMessage(type: string, content: string, extend: anyObj = {}) {
        if (!this.state.activeSession) {
            this.showTip(this.t('select_dialog'), 'error')
            return
        }

        this.socketSend({
            type,
            content,
            extend,
            session_id: this.state.activeSession,
            pathInfo: 'worker/kefu.Wsession/sendSessionMessage',
        })

        this.state.sendMessageLoading = true
    }

    /**
     * 激活 session
     */
    activeSession(type: string, session: any) {
        // 重置等待响应的请求
        this.ws.awaitResponse = []

        if (type == 'tourist') {
            if (this.state.activeTourist == session.id) return

            this.state.activeTourist = session.id
            this.state.activeSession = 0

            // 加载游客信息
            this.socketSend({
                id: session.id,
                pathInfo: 'worker/kefu.WsMixed/touristInfo',
            })
        } else {
            // 会话搜索
            if (this.state.sessionDialog.searchSession) {
                this.state.sessionDialog.searchSession = false
                this.state.sessionDialog.searchSessionKeyword = ''
            }

            if (this.state.activeSession == session.id) {
                this.messageInputFocus()
                return
            }

            this.state.activeTourist = 0
            this.state.activeSession = session.id
            this.state.unexpectedRecords = 0

            // 加载 sessionInfo
            this.socketSend({
                id: session.id,
                pathInfo: 'worker/kefu.Wsession/sessionInfo',
                readMessage: true,
            })

            // 标记消息已读
            session.unread_message_count = 0

            // 聊天记录
            this.state.sessionRecords.page = 1
            this.state.sessionRecords.nextPage = true
            this.loadSessionRecords()
        }

        // 轨迹相关属性重置
        this.state.trajectorys.page = 1
        this.state.trajectorys.nextPage = true

        // 滚动条至选中项
        nextTick(() => {
            let activeEl: HTMLElement | null = null
            if (type == 'session') {
                activeEl = document.querySelector('.sessions .session-item.active')
            } else {
                activeEl = document.querySelector('.tourist-list .tourist-item.active')
            }

            if (activeEl) {
                this.common.intersectionObserverType = type == 'session' ? 'session' : 'tourist'
                this.common.intersectionObserver.observe(activeEl)
            }
        })
    }

    /**
     * 获取客服代表的轨迹选项卡数据
     */
    getTrajectoryTabData() {
        if (!this.state.baseInfoForm.id) {
            this.showTip(this.t('select_dialog'), 'error')
            return
        }

        if (!this.state.trajectorys.nextPage || this.state.trajectorys.loading) return

        this.state.trajectorys.loading = true
        this.socketSend({
            id: this.state.baseInfoForm.id,
            page: this.state.trajectorys.page,
            sessionId: this.state.activeSession,
            pathInfo: 'worker/kefu.WsMixed/getTrajectoryData',
        })
    }

    /**
     * 获取客服代表的知识库选项卡数据
     */
    getKbsTabData() {
        if (this.state.kbs.loading) return

        if (!this.state.kbs.matchInput) {
            this.state.kbs.data = []
            return
        }

        this.state.kbs.loading = true
        this.socketSend({
            keywords: this.state.kbs.matchInput,
            pathInfo: 'worker/kefu.WsMixed/getKbsData',
        })
    }

    /**
     * 发送消息输入状态
     */
    sendMessageInputState(state: string) {
        if (this.config?.input_status_display == 'no' || (this.config?.input_status_display == 'csr' && this.state.userInfo.identity == 'csr')) {
            return
        }
        this.socketSend({
            state: state,
            sessionId: this.state.activeSession,
            sessionUserId: this.state.sessionInfo.session_user_id,
            sessionUserIdentity: this.state.sessionInfo.session_user_identity,
            pathInfo: 'worker/kefu.WsMixed/messageInputState',
            needResponse: false,
        })
    }

    /**
     * 加载会话的聊天记录
     */
    loadSessionRecords() {
        if (!this.state.activeSession) {
            this.showTip(this.t('select_dialog'), 'error')
            return
        }

        if (!this.state.sessionRecords.nextPage || this.state.sessionRecords.loading) return

        this.state.sessionRecords.loading = true

        this.socketSend({
            id: this.state.activeSession,
            page: this.state.sessionRecords.page,
            unexpectedRecords: this.state.unexpectedRecords,
            pathInfo: 'worker/kefu.Wsession/loadRecords',
            needResponse: false,
        })
    }

    /**
     * 显示提示信息
     */
    showTip(message: string, type: messageType = 'info', duration = 3000) {
        if (duration === 0) {
            this.common.elMessageInstance?.close()
            this.common.elMessageInstance = ElMessage({
                message: `【${this.config?.app_name}】${message}`,
                type,
                duration,
            })
        } else {
            ElMessage({
                message: `【${this.config?.app_name}】${message}`,
                type,
                duration,
            })
        }
    }

    /**
     * 文件上传
     */
    fileUpload(fd: FormData, params: anyObj = {}, config: AxiosRequestConfig = {}): ApiPromise {
        if (this.state.userInfo.identity == 'tourist' && this.config?.tourist_file_switch == 'no') {
            const msg = this.t('file_upload_forbidden')
            return new Promise((resolve, reject) => {
                this.showTip(msg, 'error')
                reject(msg)
            })
        }

        return upload(fd, params, config)
    }

    /**
     * 自动邀请
     */
    autoInvitation() {
        // 未开启
        if (this.config?.auto_invitation_switch != 'yes') return

        // 最大邀请次数
        if (parseInt(this.config.auto_invitation_max) > 0 && this.state.invitation.count >= parseInt(this.config.auto_invitation_max)) {
            return
        } else {
            this.state.invitation.count++
        }

        const autoInvitationTiming = parseInt(this.config.auto_invitation_timing) * 1000
        if (autoInvitationTiming) {
            this.state.invitation.timer = window.setTimeout(() => {
                this.showInvitation()
            }, autoInvitationTiming)
        }
    }

    /**
     * 显示邀请会话弹窗
     */
    showInvitation() {
        if (this.state.invitation.timer) {
            clearTimeout(this.state.invitation.timer)
        }

        // 客服代表
        if (this.state.userInfo.identity == 'csr') return

        // 已有会话
        if (this.state.userInfo.session && this.config?.auto_invitation_no_session == 'yes') return

        // 系统已经关闭
        if (!this.state.system.switch) return

        // 会话窗口已打开
        if (this.state.sessionDialog.show) return

        // 邀请已禁用
        if (this.state.invitation.disabled) return

        // 已经弹出邀请框了
        if (this.state.invitation.show) return

        // 固钉状态异常
        if (!this.state.affix.show || this.state.affix.disabled) return

        this.state.invitation.show = true
    }

    /**
     * 隐藏邀请会话弹窗
     */
    hideInvitation() {
        this.state.invitation.show = false
        this.autoInvitation()
    }

    /**
     * 清理计时器
     */
    clearWsTimer() {
        if (this.ws.timer != null) {
            clearTimeout(this.ws.timer)
        }
    }

    /**
     * 系统切至后台运行
     */
    hideSystem() {
        // 固钉
        this.state.affix.show = false
        this.state.affix.disabled = true

        // 铃声
        this.ring.disabled = true

        // 游客邀请框
        this.state.invitation.show = false
        this.state.invitation.disabled = true

        // 固钉新消息提醒
        this.state.newMessage.show = false
        this.state.newMessage.disabled = true

        // 会话弹窗
        this.state.sessionDialog.show = false
    }

    /**
     * 系统恢复前台运行
     */
    showSystem() {
        if (this.state.system.hideLock) return

        // 固钉
        this.state.affix.show = true
        this.state.affix.disabled = false

        // 铃声
        this.ring.disabled = false

        // 游客邀请框
        this.state.invitation.disabled = false

        // 固钉新消息提醒
        this.state.newMessage.disabled = false
    }

    /**
     * 创建语言切换器元素
     */
    createLanguageSwitcher(containerId: string) {
        const container = document.getElementById(containerId)
        if (!container) return

        const languageSwitcher = document.createElement('div')
        languageSwitcher.className = 'language-switcher'
        languageSwitcher.style.textAlign = 'center'
        languageSwitcher.style.padding = '10px'
        
        const switcherContent = document.createElement('div')
        switcherContent.className = 'language-switcher-content'
        switcherContent.style.display = 'flex'
        switcherContent.style.justifyContent = 'center'
        switcherContent.style.gap = '10px'
        
        this.state.language.list.forEach(lang => {
            const langButton = document.createElement('button')
            langButton.innerText = lang.label
            langButton.style.padding = '5px 10px'
            langButton.style.borderRadius = '4px'
            langButton.style.cursor = 'pointer'
            
            if (lang.value === this.state.language.current) {
                langButton.style.fontWeight = 'bold'
                langButton.style.backgroundColor = '#409eff'
                langButton.style.color = '#fff'
                langButton.style.border = 'none'
            } else {
                langButton.style.backgroundColor = '#f5f7fa'
                langButton.style.color = '#606266'
                langButton.style.border = '1px solid #dcdfe6'
            }
            
            langButton.addEventListener('click', () => {
                this.changeLang(lang.value)
                this.createLanguageSwitcher(containerId) // 重新渲染以更新选中状态
            })
            
            switcherContent.appendChild(langButton)
        })
        
        languageSwitcher.appendChild(switcherContent)
        
        // 清除现有的语言切换器并添加新的
        const existingSwitcher = container.querySelector('.language-switcher')
        if (existingSwitcher) {
            container.removeChild(existingSwitcher)
        }
        
        container.appendChild(languageSwitcher)
    }

    /**
     * 系统总开关状态调整
     */
    toggleSwitch(status: boolean) {
        this.state.system.switch = status
        this.ws.canConnect = status

        if (status) {
            this.showSystem()
            this.socketConnect()
        } else {
            this.hideSystem()
            this.state.userInfo.status = 'offline'
            this.ws.socketTask?.close()
            console.log(`【${this.config?.app_name}】系统主动关闭，WS将不会自动重新连接`)
        }
    }

    onConversation() {
        if (this.state.userInfo && this.state.userInfo.identity == 'csr') {
            this.csrConversation()
        } else {
            this.userConversation()
        }
    }

    /**
     * 客服代表打开对话页面
     */
    csrConversation() {
        // 隐藏新消息提示
        this.hideNewMessagePopover()

        if (this.common.csrTab && !this.common.csrTab.closed) {
            this.common.csrTab.focus()
        } else {
            const sessionUrl = import.meta.env.PROD ? this.config?.session_link_prod : this.config?.session_link_dev
            this.common.csrTab = window.open(sessionUrl, 'dialogueWindow')
        }
    }

    /**
     * 用户开始对话（根据配置打开弹窗、转到链接、打开会话小窗）
     */
    userConversation() {
        // 是否需要隐藏新消息提示、邀请框（取决于是否需要打开新窗口且新窗口打开成功）
        let hideFlag: boolean | Window | null = true

        // 标记新用户态
        if (!Local.get(BA_KEFU_NEW_USER)) {
            Local.set(BA_KEFU_NEW_USER, 1)
        }

        // 新窗口URL
        const sessionUrl = import.meta.env.PROD ? this.config?.session_link_prod : this.config?.session_link_dev

        // 如果已经是在单独的会话窗口，均为打开弹窗
        if (this.state.independentTab) {
            this.toggleWindow(true)
        } else if (this.config?.click_button_mode == 'dialog_and_small_window' || this.config?.click_button_mode == 'dialog_and_new_window') {
            // 打开窗口
            this.toggleWindow(true)
        } else if (this.common.userTab && !this.common.userTab.closed) {
            this.common.userTab.focus()
        } else if (this.config?.click_button_mode == 'small_window') {
            // 会话小窗
            // 小窗 top
            let top = (window.screen.availHeight - 720) / 2
            top = top > 0 ? top : 0

            // 小窗 left
            let left = (window.screen.availWidth - 900) / 2
            left = left > 0 ? left : 0

            hideFlag = this.common.userTab = window.open(
                this.addUrlParams(sessionUrl!, {
                    mode: 'small',
                    independentTab: 1,
                    baTouristToken: Local.get(BA_KEFU_TOURIST_TOKEN),
                }),
                'dialogueWindow',
                `height=720,width=900,top=${top},left=${left},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no`
            )
        } else if (this.config?.click_button_mode == 'new_window') {
            // 新窗口
            hideFlag = this.common.userTab = window.open(
                this.addUrlParams(sessionUrl!, {
                    mode: 'new',
                    independentTab: 1,
                    baTouristToken: Local.get(BA_KEFU_TOURIST_TOKEN),
                }),
                'dialogueWindow'
            )
        }

        if (hideFlag) {
            // 隐藏邀请框
            this.hideInvitation()

            // 隐藏新消息提示
            this.hideNewMessagePopover()
        }
    }

    /**
     * 用户 - 切换会话窗口显示状态
     */
    toggleWindow(status: boolean) {
        this.state.sessionDialog.show = status

        // 标记新用户态 - 有时候没走 this.userConversation 直接走到了本方法
        if (!Local.get(BA_KEFU_NEW_USER)) {
            Local.set(BA_KEFU_NEW_USER, 1)
        }

        if (status) {
            // 激活会话
            if (this.state.userInfo.session) {
                if (this.state.activeSession == this.state.userInfo.session && !isEmpty(this.state.sessionInfo)) {
                    // 标记消息阅读
                    this.socketSend({
                        pathInfo: 'worker/kefu.Wsession/readMessage',
                        sessionId: this.state.activeSession,
                        senderId: this.state.sessionInfo.session_user_id,
                        needResponse: false,
                    })

                    // 滚动到底部
                    if (this.state.sessionRecords.page == 1) {
                        this.pullMessageScrollBar()
                    }
                }
                this.activeSession('session', { id: this.state.userInfo.session })
            } else {
                // 分配客服
                this.socketSend({
                    pathInfo: 'worker/kefu.WsIndex/distribution',
                })
            }
        }
    }

    /**
     * 获取会话窗口显示状态
     */
    getWindowStatus() {
        if (this.state.userInfo.identity == 'csr') {
            return true
        }
        return this.state.sessionDialog.show
    }

    /**
     * 收到新消息
     */
    onNewMessage(newMessageContent: string) {
        // 系统已经关闭
        if (!this.state.system.switch) return

        // 有单独会话窗口标签页
        if ((this.common.csrTab && !this.common.csrTab.closed) || (this.common.userTab && !this.common.userTab.closed)) return

        // 铃声
        this.newMessageRing()

        // 窗口抖动
        this.newMessageShake()

        // WEB Notification
        const notification = this.newMessageNotification(newMessageContent, {
            tag: 'ba-kefu-notification',
            renotify: true,
        })
        if (notification) {
            notification.onclick = () => {
                this.onConversation()
                notification.close()
            }
        }

        // 以下为 popover 的显示
        if (this.state.sessionDialog.show) return // 会话窗口已打开
        if (this.state.newMessage.disabled) return // 新消息提示已经禁用
        if (!this.state.affix.show || this.state.affix.disabled) return // 固钉状态异常

        this.state.newMessage = {
            show: true,
            disabled: false,
            content: newMessageContent,
        }
    }

    /**
     * 隐藏新消息提示
     */
    hideNewMessagePopover() {
        this.state.newMessage.show = false
    }

    /**
     * 新消息铃声控制
     */
    newMessageRing() {
        if (this.config!.new_message_sound == 'no' || !this.config!.ring || this.ring.disabled) return

        if (this.ring.buffer) {
            this.playRing()
        } else {
            this.loadRing()
        }
    }

    /**
     * 新消息窗口抖动
     */
    newMessageShake() {
        if (this.config!.new_message_shake == 'no') return
        this.state.sessionDialog.shake = true
        setTimeout(() => {
            this.state.sessionDialog.shake = false
        }, 400)
    }

    /**
     * 消息通知 Notification API
     */
    newMessageNotification(title: string, options: anyObj) {
        if (document.hasFocus()) return false
        if (!this.common.notificationStatus) return false
        if (isEmpty(this.state.userInfo)) return false
        if (this.config?.new_message_notification == 'no') return false
        if (this.config?.new_message_notification == 'user' && this.state.userInfo.identity == 'csr') return false
        if (this.config?.new_message_notification == 'csr' && this.state.userInfo.identity != 'csr') return false

        const NotificationInstance = Notification || window.Notification
        // eslint-disable-next-line no-extra-boolean-cast
        if (!!NotificationInstance) {
            return new NotificationInstance(title, options)
        } else if ('mozNotification' in navigator) {
            return (navigator.mozNotification as any).createNotification(title, options.body, options.icon).show()
        } else {
            return false
        }
    }

    /**
     * 申请 Notification API 权限
     */
    applyNotificationAuth() {
        if (isEmpty(this.state.userInfo)) return
        if (this.config?.new_message_notification == 'no') return
        if (this.config?.new_message_notification == 'user' && this.state.userInfo.identity == 'csr') return
        if (this.config?.new_message_notification == 'csr' && this.state.userInfo.identity != 'csr') return

        this.common.notificationStatus = false
        const NotificationInstance = Notification || window.Notification
        // eslint-disable-next-line no-extra-boolean-cast
        if (!!NotificationInstance) {
            const permissionNow = NotificationInstance.permission
            if (permissionNow == 'granted') {
                this.common.notificationStatus = true
            } else if (permissionNow === 'default') {
                // 申请权限
                NotificationInstance.requestPermission().then((permission) => {
                    if (permission === 'granted') {
                        this.common.notificationStatus = true
                    } else if (permission === 'default') {
                        // 用户关闭授权，下次再申请
                    } else {
                        // denied
                        console.log('用户拒绝授权,不能显示 Notification API 通知')
                    }
                })
            } else {
                // denied 用户拒绝
                console.log('用户曾经拒绝显示通知或非https')
            }
        }
    }

    playRing() {
        this.ring.source = this.ring.context.createBufferSource()
        this.ring.source.buffer = this.ring.buffer
        this.ring.source.loop = false
        this.ring.source.connect(this.ring.context.destination)
        this.ring.source.start(0) // 立即播放
    }

    loadRing() {
        const url = buildRingUrl()
        const xhr = new XMLHttpRequest() // 通过XHR下载音频文件
        xhr.open('GET', url, true)
        xhr.responseType = 'arraybuffer'
        xhr.onload = () => {
            // 下载完成
            this.ring.context.decodeAudioData(
                xhr.response,
                (buffer) => {
                    // 解码成功
                    this.ring.buffer = buffer
                    this.playRing()
                },
                (e) => {
                    console.log('音频解码失败', e)
                }
            )
        }
        xhr.send()
    }

    /**
     * 发送轨迹信息
     */
    sendTrajectory(title: string, details = '', broadcast = true) {
        if (!this.ws.ready || this.state.userInfo.identity == 'csr') return
        this.socketSend({
            pathInfo: 'worker/kefu.WsMixed/trajectory',
            title,
            details,
            url: window.location.href,
            referrer: document.referrer,
            needResponse: false,
            broadcast,
        })
    }

    /**
     * 定时发送心跳
     */
    sendPing() {
        this.clearWsTimer()
        this.ws.timer = window.setTimeout(() => {
            this.socketSend({
                pathInfo: 'worker/kefu.WsMixed/ping',
            })
        }, 50000)
    }

    /**
     * 收到 ws 消息
     */
    onMessage(msg: ReceivedWsMessage) {
        this.ws.awaitResponse = this.ws.awaitResponse.filter((item) => {
            return item != msg.path
        })

        const msgFun = new Map([
            [
                'init',
                () => {
                    if (!msg.data.imUser) {
                        return this.toggleSwitch(false)
                    }

                    // 缓存游客token
                    if (msg.data.imUser.identity == 'tourist' && msg.data.imUser.token) {
                        Local.set(BA_KEFU_TOURIST_TOKEN, msg.data.imUser.token)
                    }

                    if (msg.data.imUser.identity == 'csr' && !Local.get(BA_KEFU_NEW_USER)) {
                        Local.set(BA_KEFU_NEW_USER, 1)
                    }

                    this.state.userInfo = msg.data.imUser
                    typeof this.hooks.userInitSuccess == 'function' && this.hooks.userInitSuccess(this.state.userInfo)

                    // 未读消息通知
                    if (msg.data.imUser.unreadMessage) {
                        this.onNewMessage(msg.data.imUser.unreadMessage.formatContent)
                    }

                    // 定时发送心跳
                    this.sendPing()

                    // 数据初始化
                    this.ws.ready = true
                    this.state.touristData = []
                    this.state.sessionData = []
                    this.autoInvitation()
                    this.applyNotificationAuth()
                },
            ],
            [
                'pong',
                () => {
                    // 收到 pong 再设置一个新的计时器来发送下一个心跳，能够尽可能规避浏览器节能导致的连接断开和计时器失准
                    this.sendPing()
                },
            ],
            [
                'changeStatus',
                () => {
                    // 当前用户状态改变
                    if (msg.data.id == this.state.userInfo.id && msg.data.identity == this.state.userInfo.identity) {
                        this.state.userInfo.status = msg.data.status
                    }

                    // 当前用户客服代表的状态改变
                    if (
                        this.state.sessionInfo &&
                        this.state.sessionInfo.session_user_id == msg.data.id &&
                        this.state.sessionInfo.session_user_identity == msg.data.identity
                    ) {
                        this.state.sessionInfo.session_user_status = msg.data.status
                    }

                    // 会话列表中的用户状态改变
                    for (const key in this.state.sessionData) {
                        if (
                            msg.data.id == this.state.sessionData[key].session_user_id &&
                            msg.data.identity == this.state.sessionData[key].session_user_identity
                        ) {
                            this.state.sessionData[key].session_user_status = msg.data.status
                            break
                        }
                    }

                    // 游客状态改变 - 从游客列表删除
                    if (msg.data.status == 'offline') {
                        for (const key in this.state.touristData) {
                            if (msg.data.id == this.state.touristData[key].id && msg.data.identity == this.state.touristData[key].identity) {
                                this.state.touristData.splice(parseInt(key), 1)
                                break
                            }
                        }
                    }
                },
            ],
            [
                'csrWindowInit',
                () => {
                    this.state.touristData = msg.data.tourists
                    this.state.sessionData = msg.data.sessions
                    this.state.quickReply = msg.data.quickReply

                    // 定时更新会话的消息时间
                    if (this.common.sessionsTimer) {
                        clearInterval(this.common.sessionsTimer)
                    }
                    this.common.sessionsTimer = window.setInterval(() => {
                        for (const key in this.state.sessionData) {
                            if (this.state.sessionData[key].sort_time == this.t('just_now')) {
                                this.state.sessionData[key].sort_time = this.t('minutes_ago', {count: 1})
                                continue
                            }
                            if (this.state.sessionData[key].sort_time.includes(this.t('minutes_ago', {count: ''}).replace('{count}', ''))) {
                                this.state.sessionData[key].sort_time = this.state.sessionData[key].sort_time.replace(
                                    /(\d+)分钟/,
                                    (match: string, p1: string) => {
                                        return this.t('minutes_ago', {count: parseInt(p1) + 1})
                                    }
                                )
                            }
                        }
                    }, 60000)
                },
            ],
            [
                'tourists',
                () => {
                    if (msg.data.action == 'await') {
                        for (const key in this.state.touristData) {
                            if (msg.data.user.id == this.state.touristData[key].id) {
                                this.state.touristData[key].status = 'await'
                                this.state.touristData[key].last_message = msg.data.formatMessage
                                break
                            }
                        }
                    } else if (msg.data.action == 'trajectory') {
                        for (const key in this.state.touristData) {
                            if (msg.data.user_id == this.state.touristData[key].id) {
                                this.state.touristData[key].trajectory_count++
                                this.state.touristData[key].last_trajectory_url = msg.data.url
                                break
                            }
                        }
                    } else if (msg.data.action == 'list') {
                        for (const key in msg.data.data) {
                            const tIdx = getArrayKey(this.state.touristData, 'id', msg.data.data[key].id)
                            if (tIdx === false) {
                                this.state.touristData.push(msg.data.data[key])
                            } else {
                                this.state.touristData[tIdx] = msg.data.data[key]
                            }
                        }
                    } else if (msg.data.action == 'reception' || msg.data.action == 'remove') {
                        for (const key in this.state.touristData) {
                            if (msg.data.user.id == this.state.touristData[key].id) {
                                this.state.touristData.splice(parseInt(key), 1)
                                break
                            }
                        }
                    }
                },
            ],
            [
                'csrTransfer',
                () => {
                    this.state.transfer.loading = false

                    if (msg.data.type == 'message') {
                        this.showTip(msg.data.message, msg.data.messageType)
                        return
                    }

                    if (msg.data.type == 'done') {
                        this.state.transfer.show = false
                        this.state.transfer.csrId = 0

                        this.showTip('会话转接成功~', 'success')
                        if (this.state.activeSession == msg.data.id) {
                            this.state.activeSession = 0
                            this.state.sessionInfo = {}
                        }
                    }
                },
            ],
            [
                'sessions',
                () => {
                    if (msg.data.action == 'list') {
                        for (const key in msg.data.sessions) {
                            const sIdx = getArrayKey(this.state.sessionData, 'id', msg.data.sessions[key].id)
                            if (sIdx === false) {
                                this.state.sessionData.push(msg.data.sessions[key])
                            } else {
                                this.state.sessionData[sIdx] = msg.data.sessions[key]
                            }
                        }
                    } else if (msg.data.action == 'add') {
                        // 关闭搜索 dialog
                        this.state.sessionDialog.searchDialog = false

                        // 会话是否已在列表
                        const index = this.state.sessionData.findIndex((item) => {
                            return item.id == msg.data.sessions[0].id
                        })
                        if (index === -1) {
                            this.state.sessionData = msg.data.sessions.concat(this.state.sessionData)
                        }

                        // 选中会话
                        this.activeSession('session', msg.data.sessions[0])
                    } else if (msg.data.action == 'refresh') {
                        this.state.sessionData = msg.data.sessions
                        this.activeSession('session', msg.data.sessions[0])
                    }
                },
            ],
            [
                'sessionInfo',
                () => {
                    this.state.sessionInfo = msg.data.sessionInfo
                    this.state.baseInfoForm = msg.data.sessionUserInfo

                    if (this.state.activeOptTab == 'trajectory') {
                        this.getTrajectoryTabData()
                    }

                    this.messageInputFocus()
                },
            ],
            [
                'touristInfo',
                () => {
                    this.state.sessionInfo = {}
                    this.state.baseInfoForm = msg.data.touristInfo

                    if (this.state.activeOptTab == 'trajectory') {
                        this.getTrajectoryTabData()
                    }
                },
            ],
            [
                'sessionRecords',
                () => {
                    if (this.state.activeSession != msg.data.sessionId) return

                    // 重置翻页意外消息计数
                    this.state.unexpectedRecords = 0

                    if (msg.data.page > 1) {
                        this.state.sessionRecords.data = [...this.state.sessionRecords.data, ...msg.data.messages]
                        this.state.sessionRecords.users = { ...this.state.sessionRecords.users, ...msg.data.users }
                    } else {
                        this.state.sessionRecords.users = msg.data.users
                        this.state.sessionRecords.data = msg.data.messages
                        if (!msg.data.messages.length) {
                            this.state.sessionRecords.data.push({
                                datetime: this.t('no_more_messages'),
                                data: [],
                            })
                        }

                        // 滚动到最底部
                        this.pullMessageScrollBar()
                    }
                    this.state.sessionRecords.page = msg.data.page
                    this.state.sessionRecords.nextPage = msg.data.nextPage
                    nextTick(() => {
                        this.state.sessionRecords.loading = false
                    })
                },
            ],
            [
                'distribution',
                () => {
                    this.state.userInfo.session = msg.data.session.id
                    this.onConversation()
                },
            ],
            [
                'messageInputState',
                () => {
                    if (
                        this.config?.input_status_display == 'no' ||
                        msg.data.sessionId != this.state.activeSession ||
                        (this.config?.input_status_display == 'csr' && this.state.userInfo.identity != 'csr')
                    ) {
                        return
                    }

                    this.state.sessionUserInputStatus = msg.data.state == 'input'
                },
            ],
            [
                'newMessage',
                () => {
                    this.ws.awaitResponse = this.ws.awaitResponse.filter((item) => {
                        return item != 'worker/kefu.Wsession/sendSessionMessage'
                    })

                    // 消息发送按钮的 loading 态
                    if (msg.data.message.sender_id == this.state.userInfo.id) {
                        this.state.sendMessageLoading = false
                    } else {
                        // 新消息通知
                        this.onNewMessage(msg.data.formatMessage)
                    }

                    // 访客
                    if (this.state.userInfo.identity != 'csr') {
                        // 收到强制对话的消息
                        if (!this.state.userInfo.session) {
                            this.state.userInfo.session = msg.data.session.id
                            this.onConversation()
                            return
                        }

                        // 收到客服代表接入后的首次消息
                        if (isEmpty(this.state.sessionInfo) || !this.state.sessionInfo.session_user_id) {
                            this.state.sessionInfo = msg.data.session
                        }
                    }

                    // 会话列表信息保障
                    const sessionIndex = this.state.sessionData.findIndex((item) => {
                        return item.id == msg.data.session.id
                    })
                    if (sessionIndex === -1) {
                        this.state.sessionData = [msg.data.session].concat(this.state.sessionData)
                    } else {
                        // 修改会话的最后消息和时间
                        this.state.sessionData[sessionIndex].sort_time = this.t('just_now')
                        this.state.sessionData[sessionIndex].last_message = msg.data.session.last_message

                        // 会话窗口未激活 - 未读消息数
                        if (msg.data.session.id != this.state.activeSession) {
                            this.state.sessionData[sessionIndex].unread_message_count = msg.data.session.unread_message_count
                        }

                        // 会话排序调整
                        const newOrder = this.state.sessionData.findIndex((item) => {
                            return !item.top
                        })
                        this.state.sessionData.splice(newOrder, 0, this.state.sessionData.splice(sessionIndex, 1)[0])
                    }

                    // 会话窗口激活 - 添加聊天记录
                    if (msg.data.session.id == this.state.activeSession && this.state.sessionRecords.data[0]) {
                        if (this.state.sessionRecords.data[0].datetime == this.t('no_more_messages')) {
                            this.state.sessionRecords.data[0].datetime = this.t('just_now')
                        }

                        this.state.sessionRecords.data[0].data.unshift(msg.data.message)
                        this.state.sessionRecords.users = { ...this.state.sessionRecords.users, ...msg.data.users }

                        // 消息已读
                        if (msg.data.message.sender_id != this.state.userInfo.id && this.getWindowStatus()) {
                            this.socketSend({
                                pathInfo: 'worker/kefu.Wsession/readMessage',
                                sessionId: msg.data.session.id,
                                senderId: msg.data.message.sender_id,
                                needResponse: false,
                            })
                        }

                        if (msg.data.message.sender_id == this.state.userInfo.id || this.state.sessionRecords.page == 1) {
                            this.pullMessageScrollBar()
                        }

                        // 翻页中的意外消息数累加
                        this.state.unexpectedRecords++
                    }
                },
            ],
            [
                'readMessage',
                () => {
                    if (this.state.activeSession != msg.data.sessionId) return
                    for (const key in this.state.sessionRecords.data) {
                        for (const msgKey in this.state.sessionRecords.data[key].data) {
                            if (this.state.sessionRecords.data[key].data[msgKey].sender_id == this.state.userInfo.id) {
                                this.state.sessionRecords.data[key].data[msgKey].status = 'read'
                            }
                        }
                    }
                },
            ],
            [
                'changeUserBaseInfo',
                () => {
                    // 昵称或绑定用户修改
                    if (msg.data.field == 'nickname' || msg.data.field == 'user_id') {
                        // 会话
                        if (msg.data.sessionId) {
                            if (this.state.activeSession == msg.data.sessionId) {
                                this.state.sessionInfo.nickname = msg.data.value.nickname
                            }
                            for (const key in this.state.sessionData) {
                                if (this.state.sessionData[key].id == msg.data.sessionId) {
                                    this.state.sessionData[key].avatar = msg.data.value.avatar
                                    this.state.sessionData[key].nickname = msg.data.value.nickname
                                    break
                                }
                            }
                        }

                        // 游客
                        if (msg.data.id) {
                            for (const key in this.state.touristData) {
                                if (this.state.touristData[key].id == msg.data.id) {
                                    this.state.touristData[key].nickname = msg.data.value.nickname
                                    break
                                }
                            }
                        }
                    }
                },
            ],
            [
                'searchDone',
                () => {
                    if (msg.data.page > 1) {
                        this.state.sessionDialog.searchData = this.state.sessionDialog.searchData.concat(msg.data.data)
                    } else {
                        this.state.sessionDialog.searchData = msg.data.data
                    }

                    this.state.sessionDialog.searchDataPage = msg.data.page
                    this.state.sessionDialog.searchDataNextPage = msg.data.nextPage
                },
            ],
            [
                'operates',
                () => {
                    // 收到邀请
                    if (msg.data.operate == 'dialog-invite') {
                        this.showInvitation()
                        return
                    }

                    // 被要求刷新会话资料
                    if (msg.data.operate == 'refreshSessionInfo' && msg.data.sessionId == this.state.activeSession) {
                        this.socketSend({
                            id: msg.data.sessionId,
                            pathInfo: 'worker/kefu.Wsession/sessionInfo',
                        })
                        return
                    }

                    // 被要求刷新游客资料
                    if (msg.data.operate == 'refreshTouristInfo' && msg.data.id == this.state.activeTourist) {
                        this.socketSend({
                            id: msg.data.id,
                            pathInfo: 'worker/kefu.WsMixed/touristInfo',
                        })
                        return
                    }

                    const sessionIndex = this.state.sessionData.findIndex((item) => {
                        return item.id == msg.data.id
                    })

                    if (sessionIndex !== -1 && msg.data.operate == 'top' && msg.data.status) {
                        this.state.sessionData[sessionIndex].top = true
                        this.state.sessionData[sessionIndex].sort_time = this.t('just_now')
                        this.state.sessionData.unshift(this.state.sessionData.splice(sessionIndex, 1)[0])
                        return
                    }

                    if (sessionIndex !== -1 && msg.data.operate == 'remove') {
                        this.state.sessionData.splice(sessionIndex, 1)
                        return
                    }

                    this.socketSend({
                        pathInfo: 'worker/kefu.Wsession/refreshSessionList',
                    })
                },
            ],
            [
                'trajectoryData',
                () => {
                    if (this.state.activeOptTab != 'trajectory') return
                    if (msg.data.id != this.state.baseInfoForm.id) return

                    if (msg.data.page > 1) {
                        this.state.trajectorys.data = this.state.trajectorys.data.concat(msg.data.list)
                    } else {
                        this.state.trajectorys.data = msg.data.list
                    }
                    this.state.trajectorys.loading = false
                    this.state.trajectorys.page = msg.data.page
                    this.state.trajectorys.nextPage = msg.data.nextPage
                },
            ],
            [
                'kbsData',
                () => {
                    if (this.state.activeOptTab != 'kbs') return

                    this.state.kbs.loading = false
                    this.state.kbs.data = msg.data.list
                },
            ],
            [
                'languageChanged',
                () => {
                    this.changeLang(msg.data.lang)
                    this.showTip(msg.data.message, 'success', 3000)
                },
            ],
            [
                'tip',
                () => {
                    this.showTip(msg.data.message, msg.data.type, msg.data.duration)
                },
            ],
            [
                'error',
                () => {
                    this.showTip(msg.data.message, 'error', msg.data.duration)
                },
            ],
            [
                'close',
                () => {
                    if (msg.data.message) console.log(msg.data.message)
                    return this.toggleSwitch(false)
                },
            ],
            [
                'default',
                () => {
                    console.log(msg.data)
                },
            ],
        ])

        const action = msgFun.get(msg.type) || msgFun.get('default')
        action!.call(this)

        if (!this.ws.awaitResponse.length) {
            this.state.awaitResponse = false
        }

        return true
    }
}
